Repository navigation
feat: drive redirection for the viewer, .rdp files and Windows hotplug - #2178
Conversation
There was a problem hiding this comment.
🟡 Changes recommended
Root-path and gateway-policy bypasses remain, alongside hotplug delivery and readiness issues.
5 open issues
What changed in this PR
Adds drive redirection to the viewer and .rdp configuration, backed by Windows drive discovery and hotplug support.
Changes:
- Parses drive selections and exposes viewer CLI options.
- Supports directory roots, stable drive-letter IDs, and Windows hotplug notifications.
- Integrates daemon sessions and adds tests and documentation.
| File | Description |
|---|---|
| crates/ironrdp-viewer/src/main.rs | Attaches drive backend and retains watcher. |
| crates/ironrdp-viewer/src/lib.rs | Exports Windows drive support. |
| crates/ironrdp-viewer/src/drives.rs | Builds factories and forwards hotplug events. |
| crates/ironrdp-viewer/src/cli.rs | Adds drive selection options. |
| crates/ironrdp-viewer/README.md | Documents drive options and properties. |
| crates/ironrdp-testsuite-extra/tests/client/config.rs | Tests viewer drive configuration. |
| crates/ironrdp-testsuite-core/tests/cfg.rs | Tests parsing, precedence, and serialization. |
| crates/ironrdp-rdpdr-native/src/windows/mod.rs | Exports drive APIs. |
| crates/ironrdp-rdpdr-native/src/windows/logical_drives.rs | Implements discovery, selection, and hotplug watching. |
| crates/ironrdp-rdpdr-native/src/windows/handles.rs | Accepts directory roots and validates paths. |
| crates/ironrdp-rdpdr-native/src/windows/factory.rs | Registers and selects logical drives. |
| crates/ironrdp-rdpdr-native/src/lib.rs | Exposes Windows drive APIs publicly. |
| crates/ironrdp-rdpdr-native/README.md | Documents roots and hotplug integration. |
| crates/ironrdp-rdpdr-native/Cargo.toml | Enables required Windows APIs. |
| crates/ironrdp-daemon/src/daemon.rs | Integrates selections, watchers, and reserved IDs. |
| crates/ironrdp-daemon/README.md | Documents connect-time drive selection. |
| crates/ironrdp-client/src/rdp.rs | Updates test configuration literals. |
| crates/ironrdp-client/src/config.rs | Carries drive selection through configuration. |
| crates/ironrdp-cfg/src/lib.rs | Adds drive parsing and property accessors. |
Review effort: Balanced
There was a problem hiding this comment.
The PR makes drive redirection configurable (drivestoredirect/redirectdrives) for the viewer and daemon, adds Windows logical-drive enumeration and hotplug watching, and renumbers daemon volumes to device IDs 27+. Verification confirms the two Windows frontends carry near-verbatim copies of the selection-to-factory wiring and the try_send forwarding callback (with a subtle Full/Closed retry contract), define the reserved named-drive device-ID base 27 twice while the complementary 1-26 letter layout lives in ironrdp-rdpdr-native, and diverge on display-name policy: the viewer rejects drive-letter names while the daemon does not. The daemon also ships a public DriveRedirection::contains_letter helper with no production caller. The claimed watcher startup arrival gap does not hold: in both frontends the watcher's initial snapshot precedes connection setup, so any selected volume present at snapshot time is announced by the factory's initial_drives() (selection intersected with currently …
Push a commit after addressing these findings. If no code change is needed, you may resolve inline threads and comment @github-actions review-ready to request human review.
There was a problem hiding this comment.
Drive-redirection PR adding mstsc-syntax drivestoredirect parsing, viewer/daemon wiring, and a Windows hotplug watcher. The core design is sound: device-ID layout avoids collisions, hotplug additions are gated on drive capability, and the earlier duplication was consolidated in response to review. All six candidates were independently verified against pr-head and are published. Two are questions kept open: a nonempty drivestoredirect unconditionally overrides an explicit redirectdrives:i:0 opt-out (a deliberate, tested but protocol-semantics-sensitive precedence), and the watcher's live behavior against real broadcasts and hardware is unproven. The remaining candidates are a silent drop of unrecognized drivestoredirect entries and three low-severity simplifications (duplicated ineligible computation, single-use helper, fallible usize conversions).
Push a commit after addressing these findings. If no code change is needed, you may resolve inline threads and comment @github-actions review-ready to request human review.
There was a problem hiding this comment.
PR #2178 adds drive redirection for the viewer, .rdp properties and Windows hotplug. The core design is sound: DriveRedirection parsing follows mstsc semantics with documented precedence, the path hardening keeps OBJ_DONT_REPARSE and rejects reparse/UNC/stream forms, device IDs 1-26 for letters and 27+ for named drives are collision-checked, and the drive_hotplug_available gate prevents queued events from adding capability after negotiation. All six validated specialist candidates were independently verified in the head revision and are published: the connect path synchronously probes every selected volume with no timeout, the shared drive wiring lives in the daemon crate's public API with client-session types, --rpc silently drops the new drive flags, a hotplug-watcher startup failure aborts the daemon connect, and two small duplication spots remain in ironrdp-cfg and the daemon's error responses.
Push a commit after addressing these findings. If no code change is needed, you may resolve inline threads and comment @github-actions review-ready to request human review.
) Windows can send the first IRP for a redirected device just before the `ServerDeviceAnnounceResponse` that accepts it. `ironrdp-rdpdr` ignored such IRPs without completing them, so the server waited about 30 seconds and then retried. This showed up in a live test of drive redirection (#2178) against a Windows Server session, after a reconnect. The log shows the order: ``` DeviceIoRequest { device_id: 3, major_function: Create } WARN Ignoring filesystem IRP before device announcement confirmation device_id=3 ServerDeviceAnnounceResponse { device_id: 3, result_code: STATUS_SUCCESS } (same millisecond) ... 30 s later ... DeviceIoRequest { device_id: 3, major_function: Create } ``` ## Change A device awaiting its announcement response now counts as announced, for both filesystem and printer IRPs (`Rdpdr::device_is_announced`). FreeRDP behaves the same way: it serves every registered device and only unregisters one when the server rejects it. - If the server later rejects the device, the existing rejection path still removes the drive from the backend, which cancels its pending IRPs. - A printer `Create` before acceptance is completed with `STATUS_UNSUCCESSFUL` in the channel and does not reach the backend. It would start a native print job that a rejection could leave allocated, because IRPs for rejected devices, including the close, are ignored. Other early printer IRPs complete without allocating. - IRPs for devices that were never announced, or were rejected, are still ignored, as MS-RDPEFS recommends. ## Testing - New `ironrdp-rdpdr` tests: a drive IRP before the response reaches the backend; an early IRP followed by a rejection removes the drive. New core testsuite tests: a printer IRP before the response is completed, an early printer `Create` fails without reaching the backend, and a `Create` after acceptance reaches it. All three fail without the fix. - `cargo xtask check fmt -v` and `cargo xtask check lints -v` pass, as do the tests in `ironrdp-rdpdr`, `ironrdp-rdpdr-native`, `ironrdp-testsuite-core` and `ironrdp-testsuite-extra`. - The live reconnect has not been rerun with this fix. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
a20153b to
9f8055f
Compare
- ironrdp-cfg: parse drivestoredirect with mstsc semantics (`*`, DynamicDrives, drive letters) into DriveRedirection; an explicit redirectdrives:i:0 disables it, otherwise a nonempty drivestoredirect wins and redirectdrives:i:1 alone selects every drive. Report unsupported entries. - ironrdp-client: carry the selection on RdpdrConfig::drives and ignore dynamic drive additions on a channel built without drive capability. - ironrdp-rdpdr-native: allow directory roots below a volume (opened without following reparse points, including for root opens), register drive letters as device IDs 1-26 and announce the selected mapped letters at each connection without probing volumes, and add LogicalDriveWatcher for WM_DEVICECHANGE and network-drive hotplug with retried delivery. - ironrdp-daemon: honor drivestoredirect on connect, keep a hotplug watcher for the session, and share the drive wiring with the viewer through ironrdp_daemon::drives. Configured volumes use device IDs from 27. - ironrdp-viewer: add --drive NAME,PATH, --drives, --home-drive and --dynamic-drives alongside the port options, and forward hotplugged drives to the session. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
9f8055f to
c617434
Compare
1de38a2
into
master


Drive redirection used to be reachable only through ActiveX and the daemon's fixed volume list. This makes it usable from the viewer and from standard
.rdpfiles, and adds automatic drive hotplug on Windows.Changes
ironrdp-cfg:DriveRedirectionparsesdrivestoredirectwith mstsc semantics (*= every drive including later ones,DynamicDrives,C:\;E:\). A nonemptydrivestoredirectwins; otherwiseredirectdrives:i:1selects every drive, as in FreeRDP. NewPropertySetExtaccessors for both keys plusdrive_redirection().ironrdp-client:RdpdrConfig::drivesandConfigBuilder::with_drive_redirection, filled from the property set. The client only carries the selection; frontends build the native factory.ironrdp-rdpdr-native(Windows):C:\Users\name), not onlyC:\. Paths with..,\?\/device/UNC prefixes,:or NUL are rejected, and the root is still opened withOBJ_DONT_REPARSE.WindowsRdpdrBackendFactory::with_logical_drivesregisters drive letters as device IDs 1–26 and announces the selected letters present when each connection (including reconnects) starts.LogicalDriveWatcherwatchesWM_DEVICECHANGEon a hidden top-level window, plus a 2 sGetLogicalDrivespoll for network drives, which are not broadcast. It reports arrivals and removals; arrivals are added to the shared selection.ironrdp-viewer:--drive NAME,PATH,--drives,--home-drive,--dynamic-drives; honors the.rdpkeys; attaches the RDPDR backend whenever any drive, smartcard or printer redirection is requested; forwards watcher changes asAddRdpdrDrive/RemoveRdpdrDrive.ironrdp-daemon: honorsdrivestoredirect/redirectdriveson connect (an explicitironrdp_rdpdr:i:0disables them) and keeps a watcher for the session's lifetime. Configured daemon volumes now use device IDs from 27, so they cannot collide with drive letters.Testing
cargo xtask check fmt -vandcargo xtask check lints -vpass..rdpparsing and precedence (testsuite-core/tests/cfg.rs), viewer CLI and.rdpmapping (testsuite-extra/tests/client/config.rs), root path validation, logical-drive factory, daemon factory.watcher_reports_subst_drive_arrival_and_removal(--ignored) maps a free letter withsubstand passes locally: arrival and removal are both reported.e2e::egfx_moves_onto_the_udp_tunnel_with_soft_synctimed out once under a parallel run and passed 3/3 alone; it is unrelated to this change.Merge note
This overlaps the printer redirection PR #2177 in
ironrdp-rdpdr-native(factory.rs, exports, README),ironrdp-clientconfig, and the viewer'scli.rs/main.rs. Whichever lands second needs a rebase. The device ID layout is: 0 smartcard, 1–26 drive letters, 27+ named drives, printers fromu32::MAX - 1down.🤖 Generated with Claude Code